/*******************************************************************************
 * Copyright (c) 2006 IBM
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *    Andy Clement - initial API and implementation
 *******************************************************************************/
package org.aspectj.systemtest.ajc154;

import java.lang.reflect.Field;

import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.LineNumber;
import org.aspectj.apache.bcel.classfile.LineNumberTable;
import org.aspectj.apache.bcel.classfile.Method;
import org.aspectj.apache.bcel.generic.MethodGen;
import org.aspectj.testing.XMLBasedAjcTestCase;
import org.aspectj.weaver.tools.ContextBasedMatcher;
import org.aspectj.weaver.tools.FuzzyBoolean;
import org.aspectj.weaver.tools.MatchingContext;
import org.aspectj.weaver.tools.PointcutDesignatorHandler;

import junit.framework.Test;

/**
 * These are tests for AspectJ1.5.4
 */
public class Ajc154Tests extends org.aspectj.testing.XMLBasedAjcTestCase {

	// public void testNewDesignatorsReferencePointcuts_pr205907() {
	// BeanDesignatorHandler beanHandler = new BeanDesignatorHandler();
	// Set set = new HashSet();
	// set.add(beanHandler);
	// PatternParser.setTestDesignators(set);
	// //parser.registerPointcutDesignatorHandler(beanHandler);
	// runTest("new pointcut designators in a reference pointcut");
	// }
	// public void testAfterThrowingAnnotationStyle_pr211674_1() { runTest("after throwing annotation style problem - 1");}
	// public void testAfterThrowingAnnotationStyle_pr211674_2() { runTest("after throwing annotation style problem - 2");}

	// crappy solution - see the bug
	// public void testCflowLtwProblem_pr166647_1() {
	// try {
	// runTest("ltw and cflow problem");
	// } catch (AssertionFailedError afe) {
	// // this is OK.... sadly
	// // at least lets check we warned the user it was going to happen:
	// String stderr = (getLastRunResult() == null ? "" : getLastRunResult().getStdErr());
	// // Expected line:
	// // [WeavingURLClassLoader] warning XML Defined aspects must be woven in cases where cflow pointcuts are involved.
	// // Currently the include/exclude patterns exclude 'x.Aspect2' [Xlint:mustWeaveXmlDefinedAspects]
	// assertTrue("Did not see warning about needing to weave xml defined aspects", stderr
	// .indexOf("warning XML Defined aspects must be woven in cases where cflow pointcuts are involved.") != -1);
	// assertTrue("Xlint warning was expected '[Xlint:mustWeaveXmlDefinedAspects]'", stderr
	// .indexOf("[Xlint:mustWeaveXmlDefinedAspects]") != -1);
	// }
	// }

	// Testing some issues with declare at type
	public void testDeclareAtTypeProblems_pr211052_1() {
		runTest("declare atType problems - 1");
	}

	public void testDeclareAtTypeProblems_pr211052_2() {
		runTest("declare atType problems - 2");
	}

	public void testDeclareAtTypeProblems_pr211052_3() {
		runTest("declare atType problems - 3");
	}

	public void testDeclareAtTypeProblems_pr211052_4() {
		runTest("declare atType problems - 4");
	}

	public void testDeclareAtTypeProblems_pr211052_5() {
		runTest("declare atType problems - 5");
	}

	// declare at type and binary weaving
	public void testDeclareAtTypeProblems_pr211052_6() {
		runTest("declare atType problems - 6");
	}

	public void testDeclareAtTypeProblems_pr211052_7() {
		runTest("declare atType problems - 7");
	}

	public void testNPEWithMissingAtAspectAnnotationInPointcutLibrary_pr162539_1() {
		runTest("NPE with missing @aspect annotation in pointcut library - 1");
	}

	public void testNPEWithMissingAtAspectAnnotationInPointcutLibrary_pr162539_2() {
		runTest("NPE with missing @aspect annotation in pointcut library - 2");
	}

	public void testWrongNumberOfTypeParameters_pr176991() {
		runTest("wrong number of type parameters");
	}

	public void testArgNamesDoesNotWork_pr148381_1() {
		runTest("argNames does not work - simple");
	}

	public void testArgNamesDoesNotWork_pr148381_2() {
		runTest("argNames does not work - error1");
	}

	public void testArgNamesDoesNotWork_pr148381_3() {
		runTest("argNames does not work - error2");
	}

	public void testArgNamesDoesNotWork_pr148381_4() {
		runTest("argNames does not work - error3");
	}

	public void testDecpProblemWhenTargetAlreadyImplements_pr169432_1() {
		runTest("declare parents problem when target already implements interface - 1");
	}

	public void testDecpProblemWhenTargetAlreadyImplements_pr169432_2() {
		runTest("declare parents problem when target already implements interface - 2");
	}

	public void testDecpProblemWhenTargetAlreadyImplements_pr169432_3() {
		runTest("declare parents problem when target already implements interface - 3");
	}

	public void testVariousLtwAroundProblems_pr209019_1() {
		runTest("various issues with ltw and around advice - 1");
	}

	public void testVariousLtwAroundProblems_pr209019_2() {
		runTest("various issues with ltw and around advice - 2");
	}

	public void testVariousLtwAroundProblems_pr209019_3() {
		runTest("various issues with ltw and around advice - 3");
	}

	public void testVariousLtwAroundProblems_pr209019_4() {
		runTest("various issues with ltw and around advice - 4");
	}

	public void testAbstractAnnotationStylePointcutWithContext_pr202088() {
		runTest("abstract annotation style pointcut with context");
	}

	public void testNoErrorForAtDecpInNormalClass_pr169428() {
		runTest("no error for atDecp in normal class");
	}

	public void testJarsZipsNonStandardSuffix_pr186673() {
		runTest("jars and zips with non-standard suffix");
	}

	public void testItdOnGenericInnerInterface_pr203646() {
		runTest("npe with itd on inner generic interface");
	}

	public void testItdOnGenericInnerInterface_pr203646_A() {
		runTest("npe with itd on inner generic interface - exampleA");
	}

	public void testItdOnGenericInnerInterface_pr203646_B() {
		runTest("npe with itd on inner generic interface - exampleB");
	}

	public void testItdOnGenericInnerInterface_pr203646_C() {
		runTest("npe with itd on inner generic interface - exampleC");
	}

	public void testItdOnGenericInnerInterface_pr203646_D() {
		runTest("npe with itd on inner generic interface - exampleD");
	}

	// public void testItdOnGenericInnerInterface_pr203646_E() { runTest("npe with itd on inner generic interface - exampleE");} //
	// needs parser change
	public void testItdOnGenericInnerInterface_pr203646_F() {
		runTest("npe with itd on inner generic interface - exampleF");
	}

	public void testItdOnGenericInnerInterface_pr203646_G() {
		runTest("npe with itd on inner generic interface - exampleG");
	}

	public void testItdClashForTypesFromAspectPath_pr206732() {
		runTest("itd clash for types from aspectpath");
	}

	// public void testAnnotationStyleAndMultiplePackages_pr197719() {
	// runTest("annotation style syntax and cross package extension"); }

	/**
	 * Complex test that attempts to damage a class like a badly behaved bytecode transformer would and checks if AspectJ can cope.
	 *
	 * @throws NoSuchFieldException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	public void testCopingWithGarbage_pr175806_1() throws ClassNotFoundException, SecurityException, NoSuchFieldException,
			IllegalArgumentException, IllegalAccessException {

		// Compile the program we are going to mess with
		runTest("coping with bad tables");

		// Load up the class and the method 'main' we are interested in
		JavaClass jc = getClassFrom(ajc.getSandboxDirectory(), "A");
		Method[] meths = jc.getMethods();
		Method oneWeWant = null;
		for (int i = 0; i < meths.length && oneWeWant == null; i++) {
			Method method = meths[i];
			if (method.getName().equals("main")) {
				oneWeWant = meths[i];
			}
		}

		/**
		 * For the main method: Stack=2, Locals=3, Args_size=1 0: iconst_5 1: istore_1 2: ldc #18; //String 3 4: astore_2 5:
		 * getstatic #24; //Field java/lang/System.out:Ljava/io/PrintStream; 8: aload_2 9: invokevirtual #30; //Method
		 * java/io/PrintStream.println:(Ljava/lang/String;)V 12: goto 23 15: pop 16: getstatic #24; //Field
		 * java/lang/System.out:Ljava/io/PrintStream; 19: iload_1 20: invokevirtual #33; //Method java/io/PrintStream.println:(I)V
		 * 23: return Exception table: from to target type 2 15 15 Class java/lang/Exception
		 *
		 * LineNumberTable: line 4: 0 line 6: 2 line 7: 5 line 8: 15 line 9: 16 line 11: 23 LocalVariableTable: Start Length Slot
		 * Name Signature 0 24 0 argv [Ljava/lang/String; 2 22 1 i I 5 10 2 s Ljava/lang/String;
		 */

		ConstantPool cp = oneWeWant.getConstantPool();
		// ConstantPool cpg = new ConstantPool(cp);

		// Damage the line number table, entry 2 (Line7:5) so it points to an invalid (not on an instruction boundary) position of 6
		Field ff = LineNumber.class.getDeclaredField("startPC");
		ff.setAccessible(true);
		ff.set(oneWeWant.getLineNumberTable().getLineNumberTable()[2], 6);
		// oneWeWant.getLineNumberTable().getLineNumberTable()[2].setStartPC(6);

		// Should be 'rounded down' when transforming it into a MethodGen, new position will be '5'
		// System.out.println("BEFORE\n"+oneWeWant.getLineNumberTable().toString());
		MethodGen toTransform = new MethodGen(oneWeWant, "A", cp, false);
		LineNumberTable lnt = toTransform.getMethod().getLineNumberTable();
		assertTrue("Should have been 'rounded down' to position 5 but is " + lnt.getLineNumberTable()[2].getStartPC(), lnt
				.getLineNumberTable()[2].getStartPC() == 5);
		// System.out.println("AFTER\n"+lnt.toString());
	}

	public void testCopingWithGarbage_pr175806_2() throws ClassNotFoundException {

		// Compile the program we are going to mess with
		runTest("coping with bad tables");

		// Load up the class and the method 'main' we are interested in
		JavaClass jc = getClassFrom(ajc.getSandboxDirectory(), "A");
		Method[] meths = jc.getMethods();
		Method oneWeWant = null;
		for (int i = 0; i < meths.length && oneWeWant == null; i++) {
			Method method = meths[i];
			if (method.getName().equals("main")) {
				oneWeWant = meths[i];
			}
		}
		// see previous test for dump of main method

		ConstantPool cp = oneWeWant.getConstantPool();
		// ConstantPoolGen cpg = new ConstantPoolGen(cp);

		// Damage the local variable table, entry 2 (" 2      22      1    i       I") so it points to an invalid start pc of 3
		oneWeWant.getLocalVariableTable().getLocalVariable(1).setStartPC(3);

		// Should be 'rounded down' when transforming it into a MethodGen, new position will be '2'
		// This next line will go BANG with an NPE if we don't correctly round the start pc down to 2
		new MethodGen(oneWeWant, "A", cp, true);
	}

	public void testGenericAspectGenericPointcut_pr174449() {
		runTest("problem with generic aspect and generic pointcut");
	}

	public void testGenericAspectGenericPointcut_noinline_pr174449() {
		runTest("problem with generic aspect and generic pointcut - noinline");
	}

	public void testGenericMethodsAndOrdering_ok_pr171953_2() {
		runTest("problem with generic methods and ordering - ok");
	}

	public void testGenericMethodsAndOrdering_bad_pr171953_2() {
		runTest("problem with generic methods and ordering - bad");
	}

	public void testItdAndJoinpointSignatureCollection_ok_pr171953() {
		runTest("problem with itd and join point signature collection - ok");
	}

	public void testItdAndJoinpointSignatureCollection_bad_pr171953() {
		runTest("problem with itd and join point signature collection - bad");
	}

	public void testGenericMethodsAndItds_pr171952() {
		runTest("generic methods and ITDs");
	}

	// public void testUsingDecpAnnotationWithoutAspectAnnotation_pr169428() {
	// runTest("using decp annotation without aspect annotation");}
	public void testItdsParameterizedParameters_pr170467() {
		runTest("itds and parameterized parameters");
	}

	public void testComplexGenerics_pr168044() {
		runTest("complex generics - 1");
	}

	public void testIncorrectlyMarkingFieldTransient_pr168063() {
		runTest("incorrectly marking field transient");
	}

	public void testInheritedAnnotations_pr169706() {
		runTest("inherited annotations");
	}

	public void testGenericFieldNPE_pr165885() {
		runTest("generic field npe");
	}

	public void testIncorrectOptimizationOfIstore_pr166084() {
		runTest("incorrect optimization of istore");
	}

	public void testDualParameterizationsNotAllowed_pr165631() {
		runTest("dual parameterizations not allowed");
	}

	public void testSuppressWarnings1_pr166238() {
		runTest("Suppress warnings1");
	}

	public void testSuppressWarnings2_pr166238() {
		runTest("Suppress warnings2");
	}

	public void testNullReturnedFromGetField_pr172107() {
		runTest("null returned from getField()");
	}

	// ///////////////////////////////////////
	public static Test suite() {
		return XMLBasedAjcTestCase.loadSuite(Ajc154Tests.class);
	}

	protected java.net.URL getSpecFile() {
		return getClassResource("ajc154.xml");
	}

	// ---
	private class BeanDesignatorHandler implements PointcutDesignatorHandler {

		private String askedToParse;
		public boolean simulateDynamicTest = false;

		public String getDesignatorName() {
			return "bean";
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.aspectj.weaver.tools.PointcutDesignatorHandler#parse(java.lang.String)
		 */
		public ContextBasedMatcher parse(String expression) {
			this.askedToParse = expression;
			return new BeanPointcutExpression(expression, this.simulateDynamicTest);
		}

		public String getExpressionLastAskedToParse() {
			return this.askedToParse;
		}
	}

	private class BeanPointcutExpression implements ContextBasedMatcher {

		private final String beanNamePattern;
		private final boolean simulateDynamicTest;

		public BeanPointcutExpression(String beanNamePattern, boolean simulateDynamicTest) {
			this.beanNamePattern = beanNamePattern;
			this.simulateDynamicTest = simulateDynamicTest;
		}

		public boolean couldMatchJoinPointsInType(Class aClass) {
			return true;
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.aspectj.weaver.tools.ContextBasedMatcher#couldMatchJoinPointsInType(java.lang.Class)
		 */
		public boolean couldMatchJoinPointsInType(Class aClass, MatchingContext context) {
			if (this.beanNamePattern.equals(context.getBinding("beanName"))) {
				return true;
			} else {
				return false;
			}
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.aspectj.weaver.tools.ContextBasedMatcher#mayNeedDynamicTest()
		 */
		public boolean mayNeedDynamicTest() {
			return this.simulateDynamicTest;
		}

		public FuzzyBoolean matchesStatically(MatchingContext matchContext) {
			if (this.simulateDynamicTest) {
				return FuzzyBoolean.MAYBE;
			}
			if (this.beanNamePattern.equals(matchContext.getBinding("beanName"))) {
				return FuzzyBoolean.YES;
			} else {
				return FuzzyBoolean.NO;
			}
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.aspectj.weaver.tools.ContextBasedMatcher#matchesDynamically(org.aspectj.weaver.tools.MatchingContext)
		 */
		public boolean matchesDynamically(MatchingContext matchContext) {
			return this.beanNamePattern.equals(matchContext.getBinding("beanName"));
		}
	}

}
